﻿# ZenCoding Visual Studio AddIn
# Copyright (C) 2009 Einar Egilsson
# http://tech.einaregilsson.com/2009/11/12/zen-coding-visual-studio-addin/
# 
# Portions of this program (the ZenCoding Python library) were taken from
# the ZenCoding project (http://code.google.com/p/zen-coding/)
# 
# Those parts are copyright (C) 2009 Sergey Chikuyonok (http://chikuyonok.ru)
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 
# $Id: addin.py 333 2009-12-02 08:02:43Z einar@einaregilsson.com $ 

import zenvs.references
from zenvs.settings import load_custom_settings
from ZenCoding.VisualStudio import IZenCodingVsAddIn, ZenCodingAddIn
from EnvDTE import *
from EnvDTE80 import *
from System import Object, Array, Environment
from System.IO import Path, File
from System.Windows.Forms import MessageBox, DialogResult
from zencoding import zen_core, stparser
import re

IP = '::ip::'
zen_core.profile = 'xml'
zen_core.newline = '\r\n'
zen_core.insertion_point = IP


#Constants
PROGID = 'ZenCoding.VisualStudio.ZenCodingAddIn'
HTML,CSS,XSL,COMMON,SNIPPETS,ABBREVIATIONS = 'html','css','xsl','common','snippets','abbreviations'

#Commands
WRAP = 'Wrap'
EXPAND = 'Expand'

#Resource IDS
EXPAND_ICON = 1
WRAP_ICON = 2

def cmd(name):
	return PROGID + '.' + name

class ZenCodingVSAddIn(IZenCodingVsAddIn):
	
	def __init__(self):
		self.vs = None
		self.add_in = None
		
	#(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
	def OnConnection(self, application, connect_mode, add_in, custom):
		self.vs = application
		self.add_in = add_in
		self.add_command(EXPAND, 'ZenCoding.Expand','Expands a ZenCoding pattern at the current cursor position', EXPAND_ICON)
		self.add_command(WRAP, 'ZenCoding.Wrap','Wraps the current selection in tags specified by a ZenCoding abbreviation', WRAP_ICON)
		self.UpdateSettings()
		ZenCodingAddIn.ConnectedInstance = self
		
	def add_command(self, name, pretty_name, description, resource_id):
		try:
			if self.vs.Commands.Item(cmd(name), -1):
				return
		except:
			pass
		
		context_guids = Array.CreateInstance(Object,0)
		self.vs.Commands.AddNamedCommand2(self.add_in, 
											name, 
											pretty_name, 
											description, 
											False, 
											resource_id, 
											context_guids, 
											int(vsCommandStatus.vsCommandStatusSupported) + int(vsCommandStatus.vsCommandStatusEnabled), 
											int(vsCommandStyle.vsCommandStylePictAndText), 
											vsCommandControlType.vsCommandControlTypeButton)
											
	#(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
	def QueryStatus(self, cmd_name, needed_text, status, cmd_text):
		if needed_text == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone:
			if not self.vs.ActiveDocument:
				status.Value = vsCommandStatus.vsCommandStatusUnsupported
			else:
				status.Value = int(vsCommandStatus.vsCommandStatusSupported) | int(vsCommandStatus.vsCommandStatusEnabled)
	
	#(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
	def Exec(self, cmd_name, exec_option, var_in, var_out, handled):
		if exec_option != vsCommandExecOption.vsCommandExecOptionDoDefault \
			or not self.vs.ActiveDocument \
			or not cmd_name in (cmd(EXPAND), cmd(WRAP)):
			return
		handled.Value = True
		if cmd_name == cmd(EXPAND):
			self.expand()
		elif cmd_name == cmd(WRAP):
			self.wrap()

	def UpdateSettings(self):
		zen_core.update_settings(stparser.get_settings(load_custom_settings()))
		
	#(ext_DisconnectMode disconnectMode, ref Array custom)
	def OnDisconnection(self, disconnect_mode, custom):
		ZenCodingAddIn.ConnectedInstance = None

	#(ref Array custom)
	def OnAddInsUpdate(self, custom):
		pass
	#(ref Array custom)
	def OnStartupComplete(self, custom):
		pass
	#(ref Array custom)
	def OnBeginShutdown(self, custom):
		pass

	def expand(self):
		doc = self.vs.ActiveDocument
		if not doc or doc.Selection.TopLine != doc.Selection.BottomLine: 
			return

		editPoint = doc.Selection.BottomPoint.CreateEditPoint()

		cur_line_nr = editPoint.Line
		cur_line = editPoint.GetLines(cur_line_nr, cur_line_nr + 1)
		cur_index = editPoint.LineCharOffset - 1 #1-based, while zencoding is 0 based
		cur_offset = editPoint.AbsoluteCharOffset
		
		abbr = doc.Selection.Text
		if abbr:
			result = zen_core.expand_abbreviation(abbr, self.doc_type(), 'xml')
		else:
			#abbr, start_index = zen_core.find_abbr_in_line(cur_line, cur_index)
			abbr = zen_core.extract_abbreviation(cur_line)
			if not abbr: 
				return
			result = zen_core.expand_abbreviation(abbr, self.doc_type(), 'xml')
			cur_line_pad = re.match(r'^(\s+)', cur_line)
			if cur_line_pad:
				result = zen_core.pad_string(result, cur_line_pad.group(1))

		editPoint.CharLeft(len(abbr))
		ip = IP in result and result.index(IP) or -1
		
		points = [(i, l.index(IP)+1) for i,l in enumerate(result.split('\n')) if IP in l]
		editPoint.ReplaceText(len(abbr), result.replace(IP,''), 0)	
		if points:
			line, col = points[0]
			if not '\n' in result:
				col += cur_index - len(abbr)
			doc.Selection.MoveToLineAndOffset(cur_line_nr+line, col, False);

	def get_abbreviation(self):
		from ZenCoding.VisualStudio.GUI import EnterAbbreviation
		form = EnterAbbreviation()
		if form.ShowDialog() == DialogResult.OK:
			return form.txtAbbreviation.Text
		return None	
		
	def wrap(self):
		doc = self.vs.ActiveDocument
		if not doc: 
			return

		abbr = self.get_abbreviation()
		if not abbr:
			return 
			
		editPoint = doc.Selection.TopPoint.CreateEditPoint()
		text = doc.Selection.Text
		cur_line_nr = editPoint.Line
		cur_line = editPoint.GetLines(cur_line_nr, cur_line_nr + 1)
		cur_index = editPoint.LineCharOffset - 1 #1-based, while zencoding is 0 based
		cur_offset = editPoint.AbsoluteCharOffset
		cur_line_pad = re.match(r'^(\s+)', cur_line)
		indent = ''
		if cur_line_pad:
			indent = cur_line_pad.group(1)
			lines = text.split('\n')
			for i in range(len(lines)):
				if lines[i].startswith(indent):
					lines[i] = lines[i][len(indent):]
			text = '\n'.join(lines)

		result = zen_core.wrap_with_abbreviation(abbr, text, self.doc_type(), 'xml')
		if indent:		
			result = zen_core.pad_string(result, indent)

		ip = IP in result and result.index(IP) or -1
		points = [(i, l.index(IP)+1) for i,l in enumerate(result.split('\n')) if IP in l]
		editPoint.ReplaceText(doc.Selection.BottomPoint, result.replace(IP,''), 0)			
		if points:
			line, col = points[0]
			if not '\n' in result:
				col += cur_index
			doc.Selection.MoveToLineAndOffset(cur_line_nr+line, col, False)
	
 	def doc_type(self):
		file = self.vs.ActiveDocument.ProjectItem.Name.lower()
		if file.endswith('.css'):
			return 'css'
		elif file.endswith('xsl') or file.endswith('xslt'):
			return 'xsl'
		return 'html'
		
